/*
  ESP8266 Blink by Simon Peter
  Blink the blue LED on the ESP-01 module
  This example code is in the public domain

  The blue LED on the ESP-01 module is connected to GPIO1
  (which is also the TXD pin; so we cannot use Serial.print() at the same time)

  Note that this sketch uses LED_BUILTIN to find the pin with the internal LED
*/

#define BLINKER_PRINT Serial
#define BLINKER_WIFI

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Blinker.h>
#include <TaskScheduler.h>
#include <NewPing.h> // HY-SRF05超声波(NewPing)
#include <DIYables_IRcontroller.h> // 红外接收器库(IR Remote Controller)
//#include <ArduinoJson.h>

// 声明变量与函数
extern const char *webpage PROGMEM;
extern bool isAuto;

const int LEFT = 21;
const int RIGHT = 22;
const int UP = 24;
const int DOWN = 25;
const int AUTO = 1;

const int ENV_D7 = 13; // L298N ENV-A gpio 电机速度(IN1,IN2)
const int ENV_D8 = 15; // L298N ENV-A gpio 电机速度(IN1,IN2)
const int M_D5 = 14; // L298N_IN1 + ESP_D1
const int M_D6 = 12; // L298N_IN2 + ESP_D2
const int M_D3 = 0; // L298N_IN3 + ESP_D3
const int M_D4 = 2; // L298N_IN3 + ESP_D4

// 定义L298N电机驱动板的引脚
const int LEFT_IN1 = M_D5;  // 左电机输入1  IN1
const int LEFT_IN2 = M_D6;  // 左电机输入2  IN2
const int RIGHT_IN1 = M_D3; // 右电机输入1 IN4
const int RIGHT_IN2 = M_D4; // 右电机输入2 IN3

const int Left_En = ENV_D7;   // 左电机使能 ENA
const int Right_En = ENV_D8;  // 右电机使能 ENB

void setup() {
  // 初始化串口
  initSerial();
  // 初始化gpio
  initGpio();
  // 初始化blinker
  initBlinker();
  // 初始化遥控器
  initRemoteController();
  // 自动开车
  initAutoDrive();
  // 初始化webserver(连接失败，就开启热点，使用WebServer来操控小车)
  initWebServer();
  Serial.println("setup");
}

void initSerial() {
  Serial.begin(115200);
}

void initGpio() {
  // 设置 gpio 为输出模式
  pinMode(LEFT_IN1, OUTPUT); 
  pinMode(LEFT_IN2, OUTPUT);
  pinMode(RIGHT_IN1, OUTPUT); 
  pinMode(RIGHT_IN2, OUTPUT);
  // 如果5V-EN_A/B 跳线帽接上了，则EN_A/B的电压始终为5V，意味着供给电机的电压始终为最大值。
  // 因此，如果我们不需要控制电机转速，就可以将这两个跳线帽接上，就可以省两根控制信号线。
  //pinMode(Left_En, OUTPUT); 
  //pinMode(Right_En, OUTPUT);
  //analogWrite(Left_En, 255);
  //analogWrite(Right_En, 255);
}

const char auth[] = "181eded9ea12";
const char ssid[] = "hailongqiu";
const char pswd[] = "123456aA";

// 新建智能小车前进/后退/左转/右转组件对象
BlinkerButton ButtonUp("btn-up");
BlinkerButton ButtonDown("btn-down");
BlinkerButton ButtonLeft("btn-left");
BlinkerButton ButtonRgiht("btn-right");
BlinkerButton ButtonStop("btn-stop");
BlinkerButton ButtonAuto("btn-auto");

void initBlinker() {
  #if defined(BLINKER_PRINT)
    BLINKER_DEBUG.stream(BLINKER_PRINT);
  #endif
  Blinker.begin(auth, ssid, pswd);
  ButtonUp.attach(button_up_callback);
  ButtonDown.attach(button_down_callback);
  ButtonLeft.attach(button_left_callback);
  ButtonRgiht.attach(button_right_callback);
  ButtonStop.attach(button_stop_callback);
  ButtonAuto.attach(button_auto_callback);
  Blinker.attachData(dataRead);
}

// 按下按键即会执行该函数 state: "tap"(点按); "press"(长按); "pressup"(释放)
// https://diandeng.tech/doc/arduino-support
void button_up_callback(const String & state) {
    Serial.println("button up state: " + state);
    if (state == "press") {
      setMotorState(UP);
    } else {
      stopMotors();
    }
}

void button_down_callback(const String & state) {
    Serial.println("button down state: " + state);
    if (state == "press") {
      setMotorState(DOWN);
    } else {
      stopMotors();
    }
}

void button_left_callback(const String & state) {
    Serial.println("button left state: " + state);
    if (state == "press") {
      setMotorState(LEFT);
    } else {
      stopMotors();
    }
}

void button_right_callback(const String & state) {
    Serial.println("button right state: " + state);
    if (state == "press") {
      setMotorState(RIGHT);
    } else {
      stopMotors();
    }
}

void button_stop_callback(const String & state) {
  Serial.println("button stop state: " + state);
  stopMotors();
}

void button_auto_callback(const String & state) {
  Serial.println("button auto state: " + state);
  if (state == "tap" || state == "pressup") {
    isAuto = !isAuto;
  }
}

// 如果未绑定的组件被触发，则会执行其中内容
void dataRead(const String & data) {
    Serial.println("dataRead readString: " + data);
}

ESP8266WebServer server(80);

void initWebServer() {
  //Serial.println("initWebServer webpage:" + String(webpage));

  // // 第一种方式：设置ESP8266为AP模式 
  // bool ssid_hidden = false; // 设置为true可以隐藏SSID
  // int channel = 1;          // 设置WiFi信道
  // int max_connection = 4;   // 设置最大连接数
  // bool result = WiFi.softAP(ssid, pswd, channel, ssid_hidden, max_connection);
  // Serial.println("ESP8266 is set as a WiFi Access Point with SSID: " + String(ssid));
  // if (result) {
  //   Serial.println("Access Point Started");
  //   IPAddress IP = WiFi.softAPIP();
  //   Serial.print("AP IP address: ");
  //   Serial.println(IP);
  // } else {
  //   Serial.println("Access Point Failed to Start");
  // }

  // 第二种方式：连接手机WIFI(热点). 
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pswd);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // 列出得到的區域端IP及啟動web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // 初始化Web服务器并设置主页响应
  server.on("/", handleGet);
  server.on("/start", handleStart);  // 开始运动的请求
  server.on("/stop", handleStop);    // 停止运动的请求
  server.begin();
  Serial.println("Web server started");
}

void handleStart() {
  if (server.method() == HTTP_POST) {
    String plain = server.arg("plain");
    Serial.println("handleStart plain:" + plain);
    // json库编译有问题，临时解决一下
    // StaticJsonDocument<200> doc;
    // deserializeJson(doc, server.arg("plain"));
    // int action = doc["action"].as<int>();
    int action = 0;
    if (plain.indexOf(String(LEFT)) != -1) {
      action = LEFT;
    } else if (plain.indexOf(String(RIGHT)) != -1) {
      action = RIGHT;
    } else if (plain.indexOf(String(UP)) != -1) {
      action = UP;
     } else if (plain.indexOf(String(DOWN)) != -1) {
      action = DOWN;
     } else if (plain.indexOf(String(AUTO)) != -1) {
      isAuto = !isAuto;
      server.send(200, "text/plain", "Start action received.");
      return;
     }
    setMotorState(action);
    server.send(200, "text/plain", "Start action received.");
  }
}

void handleStop() {
  if (server.method() == HTTP_POST) {
    stopMotors();
    server.send(200, "text/plain", "Stop action received.");
  }
}

void handleGet() {
  if (server.method() == HTTP_GET) {
    server.send(200, "text/html", webpage);
  }
}

#define TRIGGER_PIN  5
#define ECHO_PIN     4
#define MAX_DISTANCE 200
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
Scheduler runner; // 创建一个调度器实例
Task myTask(100, TASK_FOREVER, &backgroundTask); // 创建一个每100毫秒执行一次的任务
bool isAuto = false;

// 读取相关传感器数据，自动判断并行驶.
void initAutoDrive() {
  //runner.init(); // 初始化调度器
  runner.addTask(myTask); // 将任务添加到调度器中
  myTask.enable(); // 启用任务
}

void backgroundTask() {
  if (!isAuto) {
    return;
  }
  // 第一种方式：使用NewPing
  int distance = sonar.ping_cm();
  // 第二种方式：读取GPIO
  // long duration, distance;
  // digitalWrite(TRIGGER_PIN, LOW); 
  // delayMicroseconds(2); 
  // digitalWrite(TRIGGER_PIN, HIGH); // Pulse for 10ms to trigger ultrasonic detection 
  // delayMicroseconds(10); 
  // digitalWrite(TRIGGER_PIN, LOW); 
  // duration = pulseIn(ECHO_PIN, HIGH); // Read receiver pulse time 
  // distance = (duration / 2) / 29.1;
  // 价值千万的自动驾驶代码，哈哈.
  if ((distance >= 200) || (distance <=0)) {
    Serial.println("Out of range");
    // stopMotors();
  } else {
    Serial.print(distance); //Ourput distance
    Serial.println(" cm");
    if (distance <= 50) {
      setMotorState(RIGHT);
    } else {
      setMotorState(UP);
    }
  }
}

const int IR_RECEIVER_PIN = 16;  // D0
// 参考文档：https://blog.csdn.net/acktomas/article/details/139955566
//DIYables_IRcontroller_17 irController(IR_RECEIVER_PIN, 200); 
DIYables_IRcontroller_21 irController(IR_RECEIVER_PIN, 200); 

void initRemoteController() {
  irController.begin();
}

// the loop function runs over and over again forever
void loop() {
  Blinker.run();
  server.handleClient();
  // 根据障碍物，自己开车。
  runner.execute(); // loop内执行调度器中的所有任务
  //
  callRemoteControllerKey();
}

void callRemoteControllerKey() {
  Key21 key = irController.getKey();
  if (key != Key21::NONE) {
    switch(key) {
      case Key21::KEY_2: // UP
        Serial.println("callRemoteControllerKey key_2 UP");
        setMotorState(UP);
        break;
      case Key21::KEY_8: // DOWN
        Serial.println("callRemoteControllerKey key_8 DOWN");
        setMotorState(DOWN);
        break;
      case Key21::KEY_4: // LEFT
        Serial.println("callRemoteControllerKey key_4 LEFT");
        setMotorState(LEFT);
        break;
      case Key21::KEY_6: // RIGHT
        Serial.println("callRemoteControllerKey key_6 RIGHT");
        setMotorState(RIGHT);
        break;
      default:
        stopMotors();
        break;
    }
  }
}

// 停止所有电机
void stopMotors() {
  Serial.println("stopMotors");
  digitalWrite(LEFT_IN1, LOW);
  digitalWrite(LEFT_IN2, LOW);
  digitalWrite(RIGHT_IN1, LOW);
  digitalWrite(RIGHT_IN2, LOW);
}

void setMotorState(int action) {
  Serial.println("setMotorState action:" + String(action));
  // 根据action设置电机状态
  switch (action) {
   case LEFT: // 左转(左电机向后(LOW->停止也可以),右电机向前)
      digitalWrite(LEFT_IN1, LOW);
      digitalWrite(LEFT_IN2, HIGH); 
      digitalWrite(RIGHT_IN1, HIGH);
      digitalWrite(RIGHT_IN2, LOW);
      break;
    case RIGHT: // 右转(左电机向前,右电机向后(LOW->停止也可以))
      digitalWrite(LEFT_IN1, HIGH);
      digitalWrite(LEFT_IN2, LOW); 
      digitalWrite(RIGHT_IN1, LOW);
      digitalWrite(RIGHT_IN2, HIGH);
      break;
    case UP: // 前进(左右正转->顺时针)
      digitalWrite(LEFT_IN1, HIGH);
      digitalWrite(LEFT_IN2, LOW); 
      digitalWrite(RIGHT_IN1, HIGH);
      digitalWrite(RIGHT_IN2, LOW);
      break;
    case DOWN: // 后退(左右反转->逆时针)
      digitalWrite(LEFT_IN1, LOW);
      digitalWrite(LEFT_IN2, HIGH);
      digitalWrite(RIGHT_IN1, LOW);
      digitalWrite(RIGHT_IN2, HIGH); 
      break;
    default:
      // 停止所有电机
      stopMotors();
      break;
  }
}

// 前端HTML页面内容定义为字符串
const char *webpage PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP8266 Remote Car Control</title>
<style>
<style>
  body, html {
    height: 100%;
    margin: 0;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  #control-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  #horizontal-controls {
    display: flex;
    justify-content: center;
    margin: 20px; /* 为水平控制按钮组添加外边距 */
  }
  button {
    font-size: 2em;
    padding: 20px;
    margin: 10px; /* 为所有按钮添加外边距 */
  }
  .red-button {
    background-color: red;
    color: white; /* 设置文字颜色为白色以提高对比度 */
  }

  .blue-button {
    background-color: blue;
    color: white; /* 设置文字颜色为白色以提高对比度 */
  }

  /* 你可能还想为按钮添加一些hover效果 */
  .red-button:hover, .blue-button:hover {
    opacity: 0.8; /* 鼠标悬停时的透明度变化 */
  }
</style>
<script>
// JavaScript代码
function startCommand(action) {
  // 发送开始运动的指令到Arduino
  fetch('/start', {
    method: 'POST',
    body: JSON.stringify({ action: action }),
    headers: {
      'Content-Type': 'application/json'
    }
  });
}

function stopCommand() {
  // 发送停止运动的指令到Arduino
  fetch('/stop', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    }
  });
}

// 修改事件监听器来处理按下和松开
document.addEventListener('DOMContentLoaded', function() {
  const buttons = document.querySelectorAll('button');
  buttons.forEach(button => {
    // 鼠标事件
    button.addEventListener('mousedown', () => startCommand(button.dataset.action));
    button.addEventListener('mouseup', stopCommand);
    button.addEventListener('mouseleave', stopCommand);
    // 触摸事件
    button.addEventListener('touchstart', (e) => {
      e.preventDefault(); // 阻止触摸时屏幕的滚动
      startCommand(button.dataset.action);
    });
    button.addEventListener('touchend', stopCommand);
  });
});
</script>
</head>
<body>
<div id="control-panel">
  <button id="forward" class="blue-button" data-action="24">前进</button>
  <div id="horizontal-controls">
    <button id="left" class="blue-button" data-action="21">左转</button>
    <button id="stop" class="blue-button" data-action="0">停止</button>
    <button id="right" class="blue-button" data-action="22">右转</button>
  </div>
  <button id="backward" class="blue-button" data-action="25">后退</button>
  <button id="backward" class="blue-button" data-action="1">智障(智能避障)</button>
</div>
</body>
</html>
)rawliteral";


